import { htmlLiveReload } from '@gtramontina.com/bun-html-live-reload';
import { resolve as pathResolve } from 'node:path';
import { lstat, readdir } from 'node:fs';

const port = 4000;

const publicRoot = pathResolve('./public');
const distRoot = pathResolve('./dist');

const htmlHeaders = {
  headers: {
    'Content-Type': 'text/html',
  },
};

Bun.serve(
  htmlLiveReload(
    {
      port,
      development: true,
      async fetch(req: Request, server) {
        const { pathname } = new URL(req.url);
        let reqPath = pathname;

        try {
          let baseDir = publicRoot;
          if (reqPath.startsWith('/monaco-editor/')) {
            baseDir = distRoot;
            reqPath = reqPath.substring('/monaco-editor'.length) || '/';
          }
          const info = await parseReqPath({ reqPath, pathname }, baseDir);
          return handleReqPath(info);
        } catch (err) {
          return new Response(errMsg(err) || 'Not found', {
            status: 404,
            headers: { 'Content-Type': 'text/html' },
          });
        }
      },
    },
    {
      watchPath: publicRoot,
    },
  ),
);

type RequestPath = {
  pathname: string;
  reqPath: string;
};

type RequestPathInfo = RequestPath & {
  path: string;
  type: 'file' | 'directory';
};

function parseReqPath({ reqPath, pathname }: RequestPath, baseDir: string) {
  return new Promise<RequestPathInfo>((resolve, reject) => {
    const path = pathResolve(baseDir, reqPath.replace(/^\/+/, ''));
    lstat(path, (err, stats) => {
      if (err != null) reject(err);
      else if (stats.isFile()) {
        resolve({ reqPath, pathname, path, type: 'file' });
      } else if (stats.isDirectory()) {
        parseReqPath({ reqPath: reqPath + 'index.html', pathname }, baseDir)
          .then(resolve)
          .catch(() => resolve({ reqPath, pathname, path, type: 'directory' }));
      } else {
        reject(new Error(`Invalid path ${reqPath}`));
      }
    });
  });
}

function handleReqPath(info: RequestPathInfo) {
  const { type, path, reqPath } = info;
  if (type === 'file') return new Response(Bun.file(path));

  return new Promise<Response>((resolve, reject) => {
    readdir(path, (err, files) => {
      if (err != null) reject(err);

      resolve(handleDirectory(info, files));
    });
  });
}

function handleDirectory(
  { reqPath, pathname }: RequestPathInfo,
  files: string[],
) {
  const prefix = pathname.endsWith('/') ? pathname : pathname + '/';
  const links = files.map(it => `<li><a href="${prefix}${it}">${it}</a></li>`);

  return new Response(
    `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>${reqPath}</title>
</head>
<body>
<ul>${links.join('')}</ul>
</body>
</html>`,
    {
      headers: { 'Content-Type': 'text/html' },
    },
  );
}

function errMsg(err: unknown): string {
  if (err == null) return '';
  if (typeof err === 'string') return err;
  if (err instanceof Error) return err.message;
  return '';
}

console.log(`Bun.serve run: http://localhost:${port}`);
